# -*-coding:utf-8-*-

import json
import uuid
from datetime import datetime, timedelta
from datetime import time
from bson.code import Code
from pyrestful import mediatypes
from pyrestful.rest import get, post, delete
import handlers.base
import settings
from utils.auth import authenticated

mapfun_str = '''
function() {
    while (this.datetime >= ranks[index]) {
        ++index;
    }
    // 只有一个 value 的话是不会调用 reduce 函数的，所以为了统一，必须具化 emit 的内容而非简单的 emit this
    //emit(ranks[index], this);
    emit(ranks[index], {'data': this.data});
}
'''

'''
The documentation says "Currently, the return value from a reduce function cannot be an array (it's typically an object or a number)."
So create an object instead and wrap your array in that. Make sure also that the output of reduce is the same as the input type,
so you'll need to emit a similar value in the map operation.

- http://stackoverflow.com/questions/8175015/mongodb-mapreduce-reduce-multiple-not-supported-yet
'''
reducefun_str = '''
function(key, values) {
    var res = new Array(data_array_len);
    for (var i=0; i<res.length; ++i) {
        res[i] = 0;
    }

    values.forEach(function(v) {
        for (var i=0; i<v.data.length; ++i) {
            res[i] += v.data[i];
        }
    });

    // output type == input type is needed
    return {'data': res};
}
'''

finalizefun_str = '''
function(key, reduced_values) {
    return reduced_values;
}
'''

mapfun = Code(mapfun_str)
reducefun = Code(reducefun_str)
finalizefun = Code(finalizefun_str)

# 由远而近排列
RANK_COUNT = 12
DATA_ARRAY_LEN = 5

'''
device_statistics document structure
{
    "device_id": "DEVICE_ID",
    "datetime": "%Y-%m-%d %H:%M:%S",
    "type": "water",
    "data": [INT, INT, INT, INT, INT],
}
'''


class DevicesGuestsHandler(handlers.base.BaseHandler):
    # def __init__(self, application, request, **kwargs):
    #     super(DevicesStatisticsHandler, self).__init__(application, request, **kwargs)
    #     db = self.settings['db']
    #     c = db.get_collection('device_statistics')
    #     c.create_index('datetime')

    @authenticated
    @get(_path='/api/devices/{device_id}/statistics/{which}/{view}', _types=[str, str, str],
         _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_statistics(self, device_id, which, view):
        # TODO: Is owner of guest of this device?
        utest = self.get_argument('utest', 0)
        if 0 == utest and not self.is_owner_of_device(device_id):
            return
        try:
            self._retrieve_device_statistics(device_id, which, view)
        except Exception as e:
            self.set_status(400, str(e))

    def _retrieve_device_statistics(self, device_id, which, view):
        if 'water_v2' != which or view not in set(['day', 'week', 'month']):
            self.set_status(404)
            return
        detail = int(self.get_argument('detail', 0))
        if 0 > detail or RANK_COUNT < detail:
            self.set_status(403, 'detail must be >=0 and <=%d' % RANK_COUNT)
            return
        result = []
        if 'day' == view:
            if 0 == detail:
                result = self.get_day_overview(device_id)
            else:
                recent_day_ranks = self.get_recent_day_ranks()
                result = self.get_day_detail(device_id, recent_day_ranks[-detail] - timedelta(seconds=1))
        elif 'week' == view:
            if 0 == detail:
                result = self.get_week_overview(device_id)
            else:
                recent_week_ranks = self.get_recent_week_ranks()
                result = self.get_week_detail(device_id, recent_week_ranks[-detail] - timedelta(seconds=1))
        else:
            if 0 == detail:
                result = self.get_month_overview(device_id)
            else:
                recent_month_ranks = self.get_recent_month_ranks()
                result = self.get_month_detail(device_id, recent_month_ranks[-detail] - timedelta(seconds=1))
        self.finish(json.dumps(result))

    @post(_path='/tmp/api/devices/{device_id}/statistics/water_2', _type=[str], _produces=mediatypes.APPLICATION_JSON)
    def save_water_event(self, device_id):
        db = self.settings['db']
        c = db.get_collection('devices_statistics')
        try:
            request = json.loads(self.request.body)
            if DATA_ARRAY_LEN != len(request['data']):
                self.set_status(403, 'data array length must equal to %d' % DATA_ARRAY_LEN)
                return
            data = {}
            data['data'] = request['data']
            data['type'] = 'water'
            if request.has_key('datetime'):
                data['datetime'] = datetime.strptime(request['datetime'], '%Y-%m-%d %H:%M:%S')
            else:
                data['datetime'] = datetime.now()
            data['_id'] = str(uuid.uuid4())
            data['device_id'] = device_id
            c.insert_one(data)
            self.set_status(201)
        except Exception as e:
            self.set_status(400, str(e))

    @delete(_path='/tmp/api/devices/{device_id}/statistics/water/today', _type=[str],
            _produces=mediatypes.APPLICATION_JSON)
    def delete_today_water_event(self, device_id):
        db = self.settings['db']
        c = db.get_collection('devices_statistics')
        now = datetime.now()
        start = datetime.combine(now.date(), time())
        end = datetime.combine(now.date(), time(23, 59, 59))
        try:
            c.delete_many({'datetime': {'$lte': end, '$gte': start}, 'device_id': device_id, 'type': 'water'})
            self.set_status(204)
        except Exception as e:
            self.set_status(400, str(e))

    def is_owner_of_device(self, device_id):
        db = self.settings['db']
        devices = db.get_collection('devices')
        res = devices.find_one({'device_id': device_id, 'owner': self.user_id})
        if res is None:
            self.set_status(404, 'invalid device id')
            return False
        else:
            return True

    # 第二天零点对应的日期时间
    def get_recent_day_ranks(self, day=None, rank_count=RANK_COUNT):
        if day is None: day = datetime.now()
        next_day_start = day + timedelta(days=1)
        next_day_start = datetime.combine(next_day_start.date(), time())
        result = []
        i = 0
        while i < rank_count:
            result.append(next_day_start)
            next_day_start -= timedelta(days=1)
            i += 1
        result.reverse()
        return result

    # 下周一日零点对应的日期时间
    def get_recent_week_ranks(self, day=None, rank_count=RANK_COUNT):
        if day is None: day = datetime.now()
        next_week_start = day + timedelta(days=7 - day.weekday())
        next_week_start = datetime.combine(next_week_start.date(), time())
        result = []
        i = 0
        while i < rank_count:
            result.append(next_week_start)
            next_week_start -= timedelta(days=7)
            i += 1
        result.reverse()
        return result

    # 下月一日零点对应的日期时间
    def get_recent_month_ranks(self, day=None, rank_count=RANK_COUNT):
        if day is None: day = datetime.now()
        year = day.year
        next_month = day.month + 1
        if 12 < next_month:
            next_month = 1
            year += 1
        result = []
        i = 0
        while i < rank_count:
            next_month_start = datetime(year, next_month, 1)
            result.append(next_month_start)
            i += 1
            next_month -= 1
            if 0 == next_month:
                next_month = 12
                year -= 1
        result.reverse()
        return result

    def get_data(self, ranks, device_id):
        start = ranks[0] - timedelta(days=1)
        end = ranks[-1]
        db = self.settings['db']
        c = db.get_collection('devices_statistics')
        result = c.inline_map_reduce(mapfun, reducefun,
                                     query={'datetime': {'$lt': end, '$gte': start}, 'device_id': device_id, 'type': 'water'},
                                     scope={'index': 0, 'ranks': ranks, 'data_array_len': DATA_ARRAY_LEN},
                                     sort={'datetime': 1},
                                     finalize=finalizefun)
        return result

    def get_final_result(self, c, span=1):
        rows = []
        for a in c:
            end = a['_id'] - timedelta(seconds=1)
            start = end
            if -1 == span:
                # for the case of month
                start = datetime(start.year, start.month, 1)
            else:
                start = start - timedelta(days=span - 1)
                start = datetime.combine(start.date(), time())
            a['value']['start'] = start.strftime('%Y-%m-%d %H:%M:%S')
            a['value']['end'] = end.strftime('%Y-%m-%d %H:%M:%S')
            rows.append(a['value'])
        return rows

    # 日概况
    def get_day_overview(self, device_id):
        cc = self.get_data(self.get_recent_day_ranks(), device_id)
        return self.get_final_result(cc)

    # [OK]
    # get_day_overview()

    # 周概况
    def get_week_overview(self, device_id):
        cc = self.get_data(self.get_recent_week_ranks(), device_id)
        return self.get_final_result(cc, span=7)

    # [OK]
    # get_week_overview()

    # 月概况
    def get_month_overview(self, device_id):
        cc = self.get_data(self.get_recent_month_ranks(), device_id)
        return self.get_final_result(cc, span=-1)

    # [OK] 本月的结束时间不准
    # get_month_overview()

    # 日详情
    def get_day_detail(self, device_id, day=None):
        if day is None: day = datetime.now()
        start = datetime.combine(day.date(), time())
        end = datetime.combine(start.date(), time(23, 59, 59))
        result = []
        db = self.settings['db']
        c = db.get_collection('devices_statistics')
        for v in c.find({'datetime': {'$lt': end, '$gte': start}, 'device_id': device_id},
                        {'datetime': 1, 'duration': 1, 'data': 1, '_id': 0}):
            v['start'] = v['datetime'].strftime('%Y-%m-%d %H:%M:%S')
            v['end'] = (v['datetime'] + timedelta(seconds=v['duration'])).strftime('%Y-%m-%d %H:%M:%S')
            del v['datetime']
            result.append(v)
        return result

    # [OK]
    # get_day_detail()

    # [OK]
    # recent_day_ranks = get_recent_day_ranks()
    # print get_day_detail(recent_day_ranks[-1] - timedelta(seconds=1))


    # 周详情
    def get_week_detail(self, device_id, day=None):
        if day is None: day = datetime.now()
        recent_week_ranks = self.get_recent_week_ranks(day)
        next_week_start = recent_week_ranks[-1]
        # print 'next_week_start: ' + str(next_week_start)
        cur_week_end = next_week_start - timedelta(seconds=1)
        # print 'cur_week_end: ' + str(cur_week_end)
        recent_day_ranks = self.get_recent_day_ranks(cur_week_end, rank_count=7)
        cc = self.get_data(recent_day_ranks, device_id)
        return self.get_final_result(cc)

    # [OK]
    # get_week_detail()

    # [OK]
    # recent_week_ranks = get_recent_week_ranks()
    # print recent_week_ranks
    # for i in recent_week_ranks:
    #     print get_week_detail(i)

    # 月详情
    def get_month_detail(self, device_id, day=None):
        if day is None: day = datetime.now()
        recent_month_ranks = self.get_recent_month_ranks(day)
        next_month_start = recent_month_ranks[-1]
        cur_month_end = next_month_start - timedelta(seconds=1)
        # 一个月最多跨6周，至少4周
        recent_week_ranks = self.get_recent_week_ranks(cur_month_end, rank_count=6)
        i = 0
        while i < 2:
            if recent_week_ranks[0].month != day.month:
                recent_week_ranks.pop(0)
            i += 1
        # print 'recent_week_ranks: ' + str(recent_week_ranks)
        cc = self.get_data(recent_week_ranks, device_id)
        return self.get_final_result(cc, span=7)

        # [OK]
        # get_month_detail()

        # [!OK]
        # recent_month_ranks = get_recent_month_ranks()
        # print recent_month_ranks
        # for i in recent_month_ranks:
        #     print get_month_detail(i)
